#pragma once
#include <mutex>
#include <condition_variable>
#include "buffer.hpp"
#include <functional>
#include <unistd.h>
#include <atomic>
#include <thread>

namespace bit
{
    class AsyncLooper
    {
        using func_t = std::function<void(Buffer &buff)>;

    public:
        enum class LoopType
        {
            SAFE,   // 缓冲区不足就扩容，不会阻塞
            UN_SAFE // 缓冲区不足就阻塞，不会扩容
        };
        // 构造函数：
        AsyncLooper(func_t callback, LoopType looptype = LoopType::SAFE)
            :_callback(callback), _run_flag(true), _looptype(looptype), _thread(&AsyncLooper::threadEntry, this) {}
        // 关闭异步日志工作器：
        void stop()
        {
            // 1.将运行标志设计为false：
            _run_flag = false;
            // 2.唤醒当前可能正在休眠的工作线程，让它退出：
            _cond_consumer.notify_one();

            if (_looptype == LoopType::SAFE)
            {
                // 3.唤醒当前可能正在休眠的生产者线程，让它退出：
                _cond_producer.notify_all();
            }
            // 4.最后等待工作线程的退出：
            _thread.join();
        }
        // push数据到生产缓冲区当中：
        void push(const char *data, size_t len)
        {
            if (len > _buff_producer.sizeofBuff())
            {
                // 通过向外界抛出异常的方式，来避免用户一次性写入过多的数据：
                throw std::string("不能一次性写入超过缓冲区极限的数据");
            }

            // 能来到这里，说明输入的数据长度是合法的：
            // 多个生产者线程在向同一个生产缓冲区里面push数据时，必须互斥，避免数据的污染：
            std::unique_lock<std::mutex> lock(_mtx);
            if (_looptype == LoopType::SAFE)
            {
                // 1.进来之后先检查，这个生产缓冲区里面还能不能写数据，不能写数据了就要阻塞，等待工作线程将其唤醒：
                _cond_producer.wait(lock, [len, this]() -> bool
                                    { return !_run_flag || len <= this->_buff_producer.writeAbleSize(); });
            }

            if (_run_flag == false)
                return;
            // 2.来到这里就说明需要写数据了：
            _buff_producer.push(data, len);

            // 3.唤醒此时可能正在休眠的工作线程：
            _cond_consumer.notify_one();
        }
        // 析构函数：
        ~AsyncLooper()
        {
            stop();
        }

    private:
        // thread的入口函数：
        void threadEntry()
        {
            while (true)
            {
                // 1.先线程安全地检查生产缓冲区里面是否有数据，如果没有数据，就陷入等待：
                std::unique_lock<std::mutex> lock(_mtx);
                // 需要特别注意的是：有些时候可能生产缓冲区仍然是空的，但是用户需要退出，因此唤醒工作线程：
                _cond_consumer.wait(lock, [&]() -> bool
                                    { return !_run_flag || !_buff_producer.empty(); });
                // 此时来到这如果生产缓冲区里面仍然没有数据，就表明用户唤醒我，一定就是为了让我退出：
                if (_buff_producer.empty())
                {
                    break;
                }

                // 2.来到这里说明生产缓冲区里面一定有数据（这些数据也可以是临近退出时，还没被处理的数据），所以就需要交换生产和消费缓冲区：
                _buff_consumer.swap(_buff_producer);

                // 3.来到这里说明：（1）需要处理消费缓冲区里面的数据了；（2）生产数据的生产缓冲区里面一定为空了
                // 先解锁，因为工作线程的实际地工作不用且不应该和生产线程保持互斥的关系
                lock.unlock();

                if (_looptype == LoopType::SAFE)
                {
                    // 然后唤醒可能存在的休眠的生产线程来生产数据：
                    _cond_producer.notify_all();
                }

                // 然后调用用户自定义的任务处理函数来处理消费缓冲区里的数据：
                if (_callback)
                    _callback(_buff_consumer);
                else
                {
                    std::cout << "异步线程的回调处理函数不存在" << std::endl; // 否则就会出现这种运行起来了但是成员回调还没设置好
                    abort();
                }

                // 然后初始化消费缓冲区（所谓的初始化就是将读写指针置于起始位置）：
                _buff_consumer.reset();
            }
        }

    private:
        std::mutex _mtx;                        // 生产和生产之间要锁，当进行缓冲区交换的时候也要加锁
        std::condition_variable _cond_consumer; // 消费者条件变量
        std::condition_variable _cond_producer; // 生产者条件变量
        Buffer _buff_producer;                  // 生产者缓冲区
        Buffer _buff_consumer;                  // 消费者缓冲区

        std::atomic<bool> _run_flag; // 工作器的运行标志，如果为true，说明工作器正在运行

        LoopType _looptype;          // 异步工作器的工作模式
        // 把线程创建放到最后，要保证前边的成员都已经初始化完毕了再开始
        func_t _callback;            // 缓冲区任务的处理方式
        std::thread _thread;         // 异步工作器异步工作线程
    };
}